extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#include "zlogger.h"
#include "zvar.h"

static zVar s_nil;
zVar zVarTable::s_nil;

zVar::zVar(const zVar& val)
:m_type(EZVARType_nil)
{
	SetVar(val);
}

void zVar::SetVar(const zVar& val)
{
	SetNil();
	m_type = val.Type();
	switch (val.Type())
	{
	case EZVARType_nil:
	case EZVARType_false:
	case EZVARType_true:
	case EZVARType_int:
		v.l = val.v.l;
		break;

	case EZVARType_str:
		SetStrL( (const char*)val.Str(), val.Str().length());
		assert(v.str);
		break ;

	case EZVARType_table:
		v.table = new zVarTable(val.Table());
		assert(v.table);
		break;
	default:
		assert(0);
		break;
	}	
}

void zVar::SetTable()
{
	SetNil();
	m_type = EZVARType_table;
	v.table = new zVarTable();
}
void zVar::SetNil()
{
	if(m_type == EZVARType_str)
	{
		delete v.str;
	}
	else if(m_type == EZVARType_table)
	{
		delete v.table;
	}
	m_type = EZVARType_nil;
	v.l = 0;
}

int ZCmpVar(const zVar& a, const zVar& b)
{
	if(a.Type() != b.Type())
	{
		return a.Type() - b.Type();
	}

	switch (a.Type())
	{
	case EZVARType_nil:
	case EZVARType_false:
	case EZVARType_true:
		return 0;
		break;

	case EZVARType_int:
		return a.IntV() - b.IntV();
		break;

	case EZVARType_str:
		return bstrcmp ( (bstring) &(((zVar&)(a)).Str()), (bstring) (&((zVar&)(b)).Str()));
		break ;

	case EZVARType_table:
		return int( (&a) -(&b) );
	}
	return 0;
}


zString zVarTable::toString() const
{
	zString t = "{";
	for (unsigned long i = 0; i < count(); i++)
	{
		zVarKV& kv = get(i);
		t.formata("%s=%s,", (const char*)kv.k.toString(), (const char*)kv.v.toString());
	}
	t += "}\n";
	return t;
}

zString zVar::toString() const
{
	switch (m_type)
	{
	case EZVARType_nil:
		return zString("nil");

	case EZVARType_false:
		return zString("false");

	case EZVARType_true:
		return zString("true");

	case EZVARType_int:
		{
			zString t;
			t.format("%d", v.i);
			return t;
		}
	case EZVARType_str:
		return *v.str;

	case EZVARType_table:
		return v.table->toString();

	default:
		assert(0);
		return zString("Unknown");
	}
}

zVar& zVar::SetVArgs(const char* fmt,...)
{
	va_list arglist;

	va_start(arglist, fmt);

	SetVArgsVa(fmt,arglist);
	va_end(arglist);

	return *this;
}
void zVar::SetVArgsVa(const char* fmt,va_list vl)
{
	SetTable();
	zVarTable& t = Table();
	zVar k(1);
	while(*fmt)
	{
		switch(*fmt++)
		{
		case 'i':
			t.GetAdd(k).SetInt(va_arg(vl, int));
			break;
		case 'b':
			t.GetAdd(k).SetBool( (va_arg(vl, int)) != 0);
			break;
		case 's':
			t.GetAdd(k).SetStr(va_arg(vl, const char*));
			break;
		case 'z':
			{
				const zString * str = va_arg(vl, const zString *);
				t.GetAdd(k).SetStrL(str->data, str->length());
			}
			break;
		case 'v':
			{
				zVar* ppa=va_arg(vl, zVar*);
				if (ppa)
					t.Set(k, *ppa);
				else
					t.GetAdd(k);
			}
			break;
		}
		k.v.i++;
	}
}

zVar& zVar::SetVKV(const char* fmt,...)
{
	va_list arglist;

	va_start(arglist, fmt);

	SetVKVVa(fmt,arglist);

	va_end(arglist);
	return *this;
}
void zVar::SetVKVVa(const char* fmt,va_list vl)
{
	if (!IsTable())
	{
		SetTable();
	}
	zVarTable& t = Table();
	zVar k;
	while(*fmt)
	{
		switch(*fmt++)
		{
		case 'i':
			k.SetInt(va_arg(vl, int));
			break;
		case 'b':
			k.SetBool(va_arg(vl, int) != 0);
			break;
		case 's':
			k.SetStr(va_arg(vl, const char*));
			break;
		case 'z':
			{
				const zString * str = va_arg(vl, const zString *);
				k.SetStrL(str->data, str->length());
			}
			break;
		case 'v':
			{
				zVar* ppa=va_arg(vl, zVar*);
				if (ppa)
					k = *ppa;
				else
					k.SetNil();
			}
			break;
		}

		switch(*fmt++)
		{
		case 'i':
			t.GetAdd(k).SetInt(va_arg(vl, int));
			break;
		case 'b':
			t.GetAdd(k).SetBool(va_arg(vl, int) != 0);
			break;
		case 's':
			t.GetAdd(k).SetStr(va_arg(vl, const char*));
			break;
		case 'z':
			{
				const zString * str = va_arg(vl, const zString *);
				t.GetAdd(k).SetStrL(str->data, str->length());
			}
			break;
		case 'v':
			{
				zVar* ppa=va_arg(vl, zVar*);
				if (ppa)
					t.Set(k, *ppa);
				else
					t.GetAdd(k);
			}
			break;
		}
	}
}

const zVar& zVar::GetArgN1(int n)const
{
	if (n==1)
	{
		if (this->m_type== EZVARType_table)
		{
			const zVarTable& t=Table();
			if (t.count()>=1)
				return t.GetCI(1);
		}
		else
		{
			return *this;
		}
	}
	else if (n>1)
	{
		if (this->m_type== EZVARType_table)
		{
			const zVarTable& t=Table();
			if (t.count()>=(unsigned int)n)
				return t.GetCI(n);
		}
		else
		{
			return s_nil;
		}
	}
	return s_nil;
}

int zVar::GetArgCnt()const
{
	if (this->m_type==EZVARType_table)
	{
		return Table().count();
	}
	else if (this->IsNil())
	{
		return 0;
	}
	else
		return 1;
}

double zVar::number()const
{
	switch (this->m_type)
	{
	case EZVARType_int:
		{
			return v.i;
		}
	default:
		assert(0);
		break;
	}
	return 0;
}

const char* zVar::TypeName() const
{
	switch (this->m_type)
	{
	case EZVARType_nil:
		return "NIL";
	case EZVARType_true:
	case EZVARType_false:
		return "BOOL";
	case EZVARType_int:
		return "INT";
	case EZVARType_str:
		return "STR";
	case EZVARType_table:
		return "TABLE";	
	default:		
		return "(unknown type)";
	}
}

int zVarFromLua(struct lua_State* L, zVar& t, int idx)
{
	if (idx == 0)
	{
		t.SetNil();
		return 0;
	}
	if (idx < 0)
		idx = lua_gettop(L)+idx+1;
	int type=lua_type(L,idx);
	switch(type)
	{
	case LUA_TNIL:
	case LUA_TNONE:
		t.SetNil();
		break;
	case LUA_TNUMBER:
		{
			int k;
			lua_Number n = lua_tonumber(L,idx);
			lua_number2int(k, n);
			if (lua_Number(k)==n)
			{
				t.SetInt(k);
			}
			else
			{
				assert(0);
			}
		}
		break;
	case LUA_TSTRING:
		{
			size_t l;
			const char* str = lua_tolstring(L, idx, &l);
			t.SetStrL(str, l);
		}
		break;
	case LUA_TBOOLEAN:
		t.SetBool(lua_toboolean(L,idx)!=0);
		break;
	case LUA_TTABLE:
		{
			t.SetTable();
			zVarTable& tab=t.Table();
			lua_pushnil(L);
			while (lua_next(L,idx))
			{
				zVarKV* kv = tab.appendOne();
				zVarFromLua(L,kv->k,-2);
				zVarFromLua(L,kv->v,-1);
				lua_pop(L, 1);  //remove value
			}
			tab.quickSort();
		}
		break;
	default:
		zDbgOut("ERROR:can't support lua type[%d] to atomtype.\n",type);
		return -1;
		break;
	}
	return 0;
}

int zVarFromLuaVa(struct lua_State* L, zVar& t, int idx,int num)
{
	int n = lua_gettop(L);  /* number of arguments */
	if (idx<0)idx=n+idx+1;
	if (idx+num-1<n)n=idx+num-1;
	if (idx==0 || n<idx)
	{
		t.SetNil();
		return 0;
	}
	t.SetTable();
	zVarTable& a=t.Table();
	a.setSize(n-idx+1);

	int i;
	for (i=1;idx<=n;idx++,i++)
	{
		zVarKV& kv=a.get(i-1);
		kv.k.SetInt(i);
		zVarFromLua(L,kv.v,idx);
	}
	return i;
}

void zVarToLua(struct lua_State* L, const zVar& t)
{
	switch(t.m_type)
	{
	case EZVARType_nil:
		lua_pushnil(L);
		break;
	case EZVARType_true:
		lua_pushboolean(L,1);
		break;
	case EZVARType_false:
		lua_pushboolean(L,0);
		break;
	case EZVARType_int:
		lua_pushnumber(L,t.IntV());
		break;
	case EZVARType_str:
		{
			const zString& s=t.Str();
			lua_pushlstring(L,s,s.length());
		}
		break;
	case EZVARType_table:
		{
			const zVarTable& tab=t.Table();
			lua_newtable(L);
			int cnt=tab.count();
			for (int i=0;i<cnt;i++)
			{
				zVarKV&kv = tab.get(i);
				zVarToLua(L,kv.k);
				zVarToLua(L,kv.v);
				lua_settable(L,-3);
			}
		}
		break;
	default:
		zDbgOut("ERROR:can't support atomtype[%d] to lua.\n",t.m_type);
		lua_pushnil(L);
		break;
	}
}

int zVarVaToLua(struct lua_State* L, const zVar& t)
{
	int cnt=t.GetArgCnt();
	for (int i=1; i <= cnt; i++)
	{
		zVarToLua(L,t.GetArgN1(i));
	}
	return cnt;
}
